<?php

namespace app\common\command;

use Phinx\Db\Adapter\AdapterFactory;
use Phinx\Migration\AbstractMigration;
use Phinx\Migration\MigrationInterface;
use Phinx\Seed\AbstractSeed;
use Phinx\Util\Util;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\Output;
use think\console\input\Option;
use think\Db;
use think\migration\command\migrate\Run;

/**
 * 安装框架模块
 * Class Install
 * @package app\common\command
 */
class Install extends Command
{
    protected function configure()
    {
        // 指令配置
        $this->setName('install')->setDescription('install frame module');
        // 设置参数
        $this->addOption('module', 1, Option::VALUE_OPTIONAL, "Selection module");
        $this->addOption('remove', 2, Option::VALUE_NONE, 'uninstall module');

    }

    protected function execute(Input $input, Output $output)
    {
        $module = $input->getOption('module');
        $remove = $input->getOption('remove');
        $rootPath = app()->getRootPath();
        $intallDir = $rootPath . '/install';
        if (!is_dir($intallDir)) {
            $output->writeln("<error>install directory does not exist</error>");
        }
        if (is_null($module)) {
            if($remove){
                if(!$this->output->confirm($this->input, "Confirm remove all module? [y]/n")){
                    return false;
                }
            }
            $dirs = scandir($intallDir);
            foreach ($dirs as $dir) {
                if (($dir != '.') && ($dir != '..')) {
                    $module_dir = $intallDir . '/' . $dir;
                    if (is_dir($module_dir)) {
                        $targetDir = app()->getAppPath() . '/' . $dir;
                        if (!$remove) {
                            if (is_dir($targetDir)) {
                                $output->writeln("<error>{$dir} module exists</error>");
                            } else {
                                //复制目录
                                $this->recurse_copy($module_dir, $targetDir);
                                if (is_dir($targetDir)) {
                                    //执行迁移
                                    $migrations = $targetDir . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'migrations';
                                    $this->execMigrations($migrations);
                                    //执行填充
                                    $seeds = $targetDir . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'seeds';
                                    $this->execSeeds($seeds);
                                    $output->writeln("<info>install {$dir} module success</info>");
                                }
                            }
                        } else {
                            //回滚迁移
                            $migrations = $targetDir . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'migrations';
                            $this->execMigrations($migrations, MigrationInterface::DOWN);
                            //删除菜单填充
                            $seeds = $targetDir . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'seeds';
                            $seeds = $this->getSeeds($seeds);
                            foreach ($seeds as $seed) {
                                $class = get_class($seed);
                                if ($dir . 'menuseeder' == strtolower($class)) {
                                    $menuIds = array_column($seed->datas, 'id');
                                    Db::name('system_menu')->whereIn('id', $menuIds)->delete();
                                }
                            }
                            //删除目录
                            $this->delDirAndFile($targetDir);
                            if (is_dir($targetDir)) {
                                $output->writeln("<error>remove {$dir} fail</error>");
                            } else {
                                $output->writeln("<info>remove {$dir} success</info>");
                            }

                        }
                    }
                }
            }
        } else {
            $module_dir = $intallDir . '/' . $module;
            $targetDir = app()->getAppPath() . '/' . $module;
            if (!$remove) {
                if (is_dir($module_dir)) {
                    if (is_dir($targetDir)) {
                        $output->writeln("<error>{$module} module exists</error>");
                    } else {
                        //复制目录
                        $this->recurse_copy($module_dir, $targetDir);
                        if (is_dir($targetDir)) {
                            //执行迁移
                            $migrations = $targetDir . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'migrations';
                            $this->execMigrations($migrations);
                            //执行填充
                            $seeds = $targetDir . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'seeds';
                            $this->execSeeds($seeds);
                            $output->writeln("<info>install {$module} module success</info>");
                        }
                    }
                } else {
                    $output->writeln("<error>iinstall {$module} directory does not exist</error>");
                }
            } else {
                if ($this->output->confirm($this->input, "Confirm remove {$module} module? [y]/n")) {
                    //回滚迁移
                    $migrations = $targetDir . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'migrations';
                    $this->execMigrations($migrations, MigrationInterface::DOWN);
                    //删除菜单填充
                    $seeds = $targetDir . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'seeds';
                    $seeds = $this->getSeeds($seeds);
                    foreach ($seeds as $seed) {
                        $class = get_class($seed);
                        if ($module . 'menuseeder' == strtolower($class)) {
                            $menuIds = array_column($seed->datas, 'id');
                            Db::name('system_menu')->whereIn('id', $menuIds)->delete();
                        }
                    }

                    //删除目录
                    $this->delDirAndFile($targetDir);
                    if (is_dir($targetDir)) {
                        $output->writeln("<error>remove {$module} fail</error>");
                    } else {
                        $output->writeln("<info>remove {$module} success</info>");
                    }
                }
            }

        }
    }

    //循环删除目录和文件函数
    private function delDirAndFile($dirName)
    {
        if ($handle = opendir("$dirName")) {
            while (false !== ($item = readdir($handle))) {
                if ($item != "." && $item != "..") {
                    if (is_dir("$dirName/$item")) {
                        $this->delDirAndFile("$dirName/$item");
                    } else {
                        unlink("$dirName/$item");
                    }
                }
            }
            closedir($handle);
            rmdir($dirName);
        }
    }

    private function execSeeds($seeds)
    {
        $seeds = $this->getSeeds($seeds);
        $run = new Run();
        foreach ($seeds as $seeder) {
            $seeder->setAdapter($run->getAdapter());
            $seeder->run();
        }
    }

    private function execMigrations($migrations, $direction = MigrationInterface::UP)
    {
        $migrations = $this->getMigrations($migrations);
        ksort($migrations);
        $run = new Run();
        foreach ($migrations as $migration) {
            if (MigrationInterface::DOWN === $direction) {
                $proxyAdapter = AdapterFactory::instance()->getWrapper('proxy', $run->getAdapter());
                $migration->setAdapter($proxyAdapter);
                $migration->change();
                $proxyAdapter->executeInvertedCommands();
            } else {
                $migration->setAdapter($run->getAdapter());
                $migration->change();
            }
        }
    }

    /**
     * 获取目录下的所有填充文件
     * @param $path 目录
     * @return Seeder[]
     */
    public function getSeeds($path)
    {
        $phpFiles = glob($path . DIRECTORY_SEPARATOR . '*.php', defined('GLOB_BRACE') ? GLOB_BRACE : 0);

        // filter the files to only get the ones that match our naming scheme
        $fileNames = [];
        /** @var Seeder[] $seeds */
        $seeds = [];

        foreach ($phpFiles as $filePath) {
            if (Util::isValidSeedFileName(basename($filePath))) {
                // convert the filename to a class name
                $class = pathinfo($filePath, PATHINFO_FILENAME);
                $fileNames[$class] = basename($filePath);

                // load the seed file
                /** @noinspection PhpIncludeInspection */
                require_once $filePath;
                if (!class_exists($class)) {
                    throw new \InvalidArgumentException(sprintf('Could not find class "%s" in file "%s"', $class, $filePath));
                }

                // instantiate it
                $seed = new $class($this->input, $this->output);

                if (!($seed instanceof AbstractSeed)) {
                    throw new \InvalidArgumentException(sprintf('The class "%s" in file "%s" must extend \Phinx\Seed\AbstractSeed', $class, $filePath));
                }

                $seeds[$class] = $seed;
            }
        }

        ksort($seeds);
        $seeds;

        return $seeds;
    }

    /**
     * 获取目录下的所有迁移文件
     * @param $path 目录
     * @return Migrator[]
     */
    protected function getMigrations($path)
    {
        $phpFiles = glob($path . DIRECTORY_SEPARATOR . '*.php', defined('GLOB_BRACE') ? GLOB_BRACE : 0);

        // filter the files to only get the ones that match our naming scheme
        $fileNames = [];
        /** @var Migrator[] $versions */
        $versions = [];

        foreach ($phpFiles as $filePath) {
            if (Util::isValidMigrationFileName(basename($filePath))) {
                $version = Util::getVersionFromFileName(basename($filePath));

                if (isset($versions[$version])) {
                    throw new \InvalidArgumentException(sprintf('Duplicate migration - "%s" has the same version as "%s"', $filePath, $versions[$version]->getVersion()));
                }

                // convert the filename to a class name
                $class = Util::mapFileNameToClassName(basename($filePath));

                if (isset($fileNames[$class])) {
                    throw new \InvalidArgumentException(sprintf('Migration "%s" has the same name as "%s"', basename($filePath), $fileNames[$class]));
                }

                $fileNames[$class] = basename($filePath);

                // load the migration file
                /** @noinspection PhpIncludeInspection */
                require_once $filePath;
                if (!class_exists($class)) {
                    throw new \InvalidArgumentException(sprintf('Could not find class "%s" in file "%s"', $class, $filePath));
                }

                // instantiate it
                $migration = new $class($version, $this->input, $this->output);

                if (!($migration instanceof AbstractMigration)) {
                    throw new \InvalidArgumentException(sprintf('The class "%s" in file "%s" must extend \Phinx\Migration\AbstractMigration', $class, $filePath));
                }

                $versions[$version] = $migration;
            }
        }

        ksort($versions);

        return $versions;
    }

    private function recurse_copy($src, $dst)
    {
        $dir = opendir($src);
        @mkdir($dst);
        while (false !== ($file = readdir($dir))) {
            if (($file != '.') && ($file != '..')) {
                if (is_dir($src . '/' . $file)) {
                    $this->recurse_copy($src . '/' . $file, $dst . '/' . $file);
                } else {
                    copy($src . '/' . $file, $dst . '/' . $file);
                }
            }
        }
        closedir($dir);
    }
}
